﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace iNet.Media.Imaging.Exif
{
	/// <summary>
	/// EXIF 影像中繼資料。
	/// </summary>
	public class ExifImageMetadata : ImageMetadataBase, IExifMetadata
	{
		#region 靜態欄位
		/// <summary>
		/// 適用於 IMetadata.Aperture 屬性的 EXIF Tag 識別碼。
		/// </summary>
		public static readonly ReadOnlyCollection<ExifTagKey> ApertureTagKeys = new ReadOnlyCollection<ExifTagKey>(new ExifTagKey[]{
			new ExifTagKey(Ifd.ExifIfd, 33437),
		});
		/// <summary>
		/// 適用於 IMetadata.Author 屬性的 EXIF Tag 識別碼。
		/// </summary>
		public static readonly ReadOnlyCollection<ExifTagKey> AuthorTagKeys = new ReadOnlyCollection<ExifTagKey>(new ExifTagKey[]{
			new ExifTagKey(Ifd.Ifd0, 0x9c9d),
			new ExifTagKey(Ifd.ExifIfd, 315),
		});
		/// <summary>
		/// 適用於 IMetadata.CameraManufacturer 屬性的 EXIF Tag 識別碼。
		/// </summary>
		public static readonly ReadOnlyCollection<ExifTagKey> CameraManufacturerTagKeys = new ReadOnlyCollection<ExifTagKey>(new ExifTagKey[]{
			new ExifTagKey(Ifd.Ifd0, 0x010f),
		});
		/// <summary>
		/// 適用於 IMetadata.CameraModel 屬性的 EXIF Tag 識別碼。
		/// </summary>
		public static readonly ReadOnlyCollection<ExifTagKey> CameraModelTagKeys = new ReadOnlyCollection<ExifTagKey>(new ExifTagKey[]{
			new ExifTagKey(Ifd.Ifd0, 0x0110),
		});
		/// <summary>
		/// 適用於 IMetadata.Comment 屬性的 EXIF Tag 識別碼。
		/// </summary>
		public static readonly ReadOnlyCollection<ExifTagKey> CommentTagKeys = new ReadOnlyCollection<ExifTagKey>(new ExifTagKey[]{
			new ExifTagKey(Ifd.Ifd0, 0x9c9c),
		});
		/// <summary>
		/// 適用於 IMetadata.Copyright 屬性的 EXIF Tag 識別碼。
		/// </summary>
		public static readonly ReadOnlyCollection<ExifTagKey> CopyrightTagKeys = new ReadOnlyCollection<ExifTagKey>(new ExifTagKey[]{
			new ExifTagKey(Ifd.Ifd0, 33432),
		});
		/// <summary>
		/// 適用於 IMetadata.CreationTime 屬性的 EXIF Tag 識別碼。
		/// </summary>
		public static readonly ReadOnlyCollection<ExifTagKey> CreationTimeTagKeys = new ReadOnlyCollection<ExifTagKey>(new ExifTagKey[]{
			new ExifTagKey(Ifd.ExifIfd, 36867),
			new ExifTagKey(Ifd.ExifIfd, 36868),
		});
		/// <summary>
		/// 適用於 IMetadata.ExposureCompensation 屬性的 EXIF Tag 識別碼。
		/// </summary>
		public static readonly ReadOnlyCollection<ExifTagKey> ExposureCompensationTagKeys = new ReadOnlyCollection<ExifTagKey>(new ExifTagKey[]{
			new ExifTagKey(Ifd.ExifIfd, 37380),
		});
		/// <summary>
		/// 適用於 IMetadata.ExposureProgram 屬性的 EXIF Tag 識別碼。
		/// </summary>
		public static readonly ReadOnlyCollection<ExifTagKey> ExposureProgramTagKeys = new ReadOnlyCollection<ExifTagKey>(new ExifTagKey[]{
			new ExifTagKey(Ifd.ExifIfd, 34850),
		});
		/// <summary>
		/// 適用於 IMetadata.FlashState 屬性的 EXIF Tag 識別碼。
		/// </summary>
		public static readonly ReadOnlyCollection<ExifTagKey> FlashStateTagKeys = new ReadOnlyCollection<ExifTagKey>(new ExifTagKey[]{
			new ExifTagKey(Ifd.ExifIfd, 37385),
		});
		/// <summary>
		/// 適用於 IMetadata.FocalLength 屬性的 EXIF Tag 識別碼。
		/// </summary>
		public static readonly ReadOnlyCollection<ExifTagKey> FocalLengthTagKeys = new ReadOnlyCollection<ExifTagKey>(new ExifTagKey[]{
			new ExifTagKey(Ifd.ExifIfd, 37386),
		});
		/// <summary>
		/// 適用於 IMetadata.FocalLengthIn35mmFilm 屬性的 EXIF Tag 識別碼。
		/// </summary>
		public static readonly ReadOnlyCollection<ExifTagKey> FocalLengthIn35mmFilmTagKeys = new ReadOnlyCollection<ExifTagKey>(new ExifTagKey[]{
			new ExifTagKey(Ifd.ExifIfd, 41989),
		});
		private static readonly ExifTagKey[] GpsCoordinateTagKeys = new ExifTagKey[]{
			new ExifTagKey(Ifd.GpsIfd, 6),	// GPSAltitude
			new ExifTagKey(Ifd.GpsIfd, 5),	// GPSAltitudeRef
			new ExifTagKey(Ifd.GpsIfd, 2),	// GPSLatitude
			new ExifTagKey(Ifd.GpsIfd, 1),	// GPSLatitudeRef
			new ExifTagKey(Ifd.GpsIfd, 4),	// GPSLongitude
			new ExifTagKey(Ifd.GpsIfd, 3),	// GPSLongitudeRef
		};
		/// <summary>
		/// 適用於 IMetadata.ISO 屬性的 EXIF Tag 識別碼。
		/// </summary>
		public static readonly ReadOnlyCollection<ExifTagKey> ISOTagKeys = new ReadOnlyCollection<ExifTagKey>(new ExifTagKey[]{
			new ExifTagKey(Ifd.ExifIfd, 34855),
		});
		/// <summary>
		/// 適用於 IMetadata.Keywords 屬性的 EXIF Tag 識別碼。
		/// </summary>
		public static readonly ReadOnlyCollection<ExifTagKey> KeywordsTagKeys = new ReadOnlyCollection<ExifTagKey>(new ExifTagKey[]{
			new ExifTagKey(Ifd.Ifd0, 0x9c9e),
		});
		/// <summary>
		/// 適用於 IMetadata.LastWriteTime 屬性的 EXIF Tag 識別碼。
		/// </summary>
		public static readonly ReadOnlyCollection<ExifTagKey> LastWriteTimeTagKeys = new ReadOnlyCollection<ExifTagKey>(new ExifTagKey[]{
			new ExifTagKey(Ifd.Ifd0, 306),
		});
		/// <summary>
		/// 適用於 IMetadata.LensManufacturer 屬性的 EXIF Tag 識別碼。
		/// </summary>
		public static readonly ReadOnlyCollection<ExifTagKey> LensManufacturerTagKeys = new ReadOnlyCollection<ExifTagKey>(new ExifTagKey[]{
			new ExifTagKey(Ifd.ExifIfd, 0xa433),
		});
		/// <summary>
		/// 適用於 IMetadata.LensModel 屬性的 EXIF Tag 識別碼。
		/// </summary>
		public static readonly ReadOnlyCollection<ExifTagKey> LensModelTagKeys = new ReadOnlyCollection<ExifTagKey>(new ExifTagKey[]{
			new ExifTagKey(Ifd.ExifIfd, 0xa434),
		});
		/// <summary>
		/// 適用於 IMetadata.Orientation 屬性的 EXIF Tag 識別碼。
		/// </summary>
		public static readonly ReadOnlyCollection<ExifTagKey> OrientationTagKeys = new ReadOnlyCollection<ExifTagKey>(new ExifTagKey[]{
			new ExifTagKey(Ifd.Ifd0, 274),
		});
		/// <summary>
		/// 適用於 IMetadata.ShutterSpeed 屬性的 EXIF Tag 識別碼。
		/// </summary>
		public static readonly ReadOnlyCollection<ExifTagKey> ShutterSpeedTagKeys = new ReadOnlyCollection<ExifTagKey>(new ExifTagKey[]{
			new ExifTagKey(Ifd.ExifIfd, 33434),
		});
		/// <summary>
		/// 適用於 IMetadata.Software 屬性的 EXIF Tag 識別碼。
		/// </summary>
		public static readonly ReadOnlyCollection<ExifTagKey> SoftwareTagKeys = new ReadOnlyCollection<ExifTagKey>(new ExifTagKey[]{
			new ExifTagKey(Ifd.Ifd0, 305),
		});
		/// <summary>
		/// 適用於 IMetadata.Subject 屬性的 EXIF Tag 識別碼。
		/// </summary>
		public static readonly ReadOnlyCollection<ExifTagKey> SubjectTagKeys = new ReadOnlyCollection<ExifTagKey>(new ExifTagKey[]{
			new ExifTagKey(Ifd.Ifd0, 0x9c9f),
		});
		/// <summary>
		/// 適用於 IMetadata.Title 屬性的 EXIF Tag 識別碼。
		/// </summary>
		public static readonly ReadOnlyCollection<ExifTagKey> TitleTagKeys = new ReadOnlyCollection<ExifTagKey>(new ExifTagKey[]{
			new ExifTagKey(Ifd.Ifd0, 0x9c9b),
			new ExifTagKey(Ifd.Ifd0, 270),
		});

		// 鏡頭相關標籤
		static readonly ExifTagKey NikonLensTagKey = new ExifTagKey(Ifd.NikonMakerNoteType3, 0x84);
		static readonly ExifTagKey NikonLensTypeTagKey = new ExifTagKey(Ifd.NikonMakerNoteType3, 0x83);
		#endregion
		#region 欄位
		readonly Dictionary<ExifTagKey, ExifTag> _ExifTagMap = new Dictionary<ExifTagKey, ExifTag>();
		IList<ExifTag> _ExifTags;
		Ifd? _MakerNoteType;
		#endregion
		#region 建構子
		/// <summary>
		/// 初始化新的 ExifImageMetadata 執行個體。
		/// </summary>
		public ExifImageMetadata()
		{
			ObservableCollection<ExifTag> tagList = new ObservableCollection<ExifTag>();
			tagList.CollectionChanged += this.OnExifTagListChanged;
			_ExifTags = tagList;
		}
		/// <summary>
		/// 初始化新的 ExifImageMetadata 執行個體。
		/// </summary>
		/// <param name="tags">EXIF Tag 列表。</param>
		/// <param name="isReadOnly">是否為唯讀。</param>
		public ExifImageMetadata(IEnumerable<ExifTag> tags, bool isReadOnly)
			: base(null, isReadOnly)
		{
			if (tags != null)
			{
				foreach (ExifTag tag in tags)
				{
					if (tag != null)
						_ExifTagMap[tag.Key] = tag;
				}
			}
			if (isReadOnly)
				_ExifTags = new ReadOnlyCollection<ExifTag>(tags.ToList());
			else
			{
				ObservableCollection<ExifTag> tagList = (tags != null ? new ObservableCollection<ExifTag>(tags) : new ObservableCollection<ExifTag>());
				tagList.CollectionChanged += this.OnExifTagListChanged;
				_ExifTags = tagList;
			}
		}
		/// <summary>
		/// 初始化新的 ExifImageMetadata 執行個體。
		/// </summary>
		/// <param name="template">範本中繼資料。</param>
		/// <param name="isReadOnly">是否為唯讀。</param>
		public ExifImageMetadata(IMetadata template, bool isReadOnly)
			: base(template, isReadOnly)
		{
			if (isReadOnly)
				_ExifTags = new ReadOnlyCollection<ExifTag>(_ExifTagMap.Values.ToArray());
			else
			{
				ObservableCollection<ExifTag> tagList = new ObservableCollection<ExifTag>(_ExifTagMap.Values);
				tagList.CollectionChanged += this.OnExifTagListChanged;
				_ExifTags = tagList;
			}
		}
		#endregion
		#region AddTag
		/// <summary>
		/// 加入新的 EXIF Tag。
		/// </summary>
		/// <param name="bitConverter">提供位元組轉換的 BitConverter 物件，Null (Visual Basic 為 Nothing) 表示使用 Little-Endian 的 BitConverter。</param>
		/// <param name="key">EXIF Tag 的識別碼。</param>
		/// <param name="value">Tag 的內容值。</param>
		/// <exception cref="ArgumentException">value 為不支援的型別。</exception>
		/// <exception cref="ArgumentNullException">value 為 Null (Visual Basic 為 Nothing)。</exception>
		/// <exception cref="InvalidOperationException">中繼資料為唯讀。</exception>
		protected void AddTag(BitConverter bitConverter, ExifTagKey key, object value)
		{
			this.AddTag(bitConverter, key.IFD, key.ID, value);
		}
		/// <summary>
		/// 加入新的 EXIF Tag。
		/// </summary>
		/// <param name="bitConverter">提供位元組轉換的 BitConverter 物件，Null (Visual Basic 為 Nothing) 表示使用 Little-Endian 的 BitConverter。</param>
		/// <param name="ifd">所屬的 IFD。</param>
		/// <param name="id">Tag ID。</param>
		/// <param name="value">Tag 的內容值。</param>
		/// <exception cref="ArgumentException">value 為不支援的型別。</exception>
		/// <exception cref="ArgumentNullException">value 為 Null (Visual Basic 為 Nothing)。</exception>
		/// <exception cref="InvalidOperationException">中繼資料為唯讀。</exception>
		protected void AddTag(BitConverter bitConverter, Ifd ifd, ushort id, object value)
		{
			//確認狀態
			if (this.IsReadOnly)
				throw new InvalidOperationException();

			//轉換資料
			if (value is string[])
			{
				//轉成單一字串
				string[] array = (value as string[]);
				if (array.Length > 0)
				{
					StringBuilder buffer = new StringBuilder();
					buffer.Append(array[0]);
					for (int i = 1; i < array.Length; ++i)
						buffer.Append(";" + array[i]);
					value = buffer.ToString();
				}
				else
					value = "";

				//轉成位元組
				value = this.ConvertFromUnicodeString(value as string);
			}

			//加入 Tag
			_ExifTags.Add(new ExifTag(bitConverter, ifd, id, value));
		}
		#endregion
		#region Aperture
		/// <summary>
		/// 取得或設定光圈值。
		/// </summary>
		public override double? Aperture
		{
			get
			{
				URational? value = this.GetValue<URational?>(this.FindTags(ApertureTagKeys));
				if (value != null)
					return value.Value.ToDouble();
				return null;
			}
			set
			{
				this.RemoveTags(ApertureTagKeys);
				if (value != null && !double.IsNaN(value.Value))
				{
					if (value.Value < 0)
						throw new ArgumentOutOfRangeException();
					this.AddTag(null, ApertureTagKeys[0], URational.FromDouble(value.Value));
				}
			}
		}
		#endregion
		#region Author
		/// <summary>
		/// 取得或設定影像的作者。
		/// </summary>
		public override string[] Authors
		{
			get
			{
				//搜尋 Tag
				IList<ExifTag> tags = this.FindTags(AuthorTagKeys);

				//轉成字串
				if (tags.Count >= 1 && tags[0].Type == ExifTagType.Byte)
					return this.ConvertToUnicodeString(tags[0].Data).Split(';');
				return this.GetStringArray(tags);
			}
			set
			{
				this.RemoveTags(AuthorTagKeys);
				if (value != null)
					this.AddTag(null, AuthorTagKeys[0], value);
			}
		}
		#endregion
		#region CameraManufacturer
		/// <summary>
		/// 取得或設定相機製造商。
		/// </summary>
		public override string CameraManufacturer
		{
			get
			{
				return this.GetValue<string>(this.FindTags(CameraManufacturerTagKeys));
			}
			set
			{
				this.RemoveTags(CameraManufacturerTagKeys);
				if (value != null)
					this.AddTag(null, CameraManufacturerTagKeys[0], value);
			}
		}
		#endregion
		#region CameraModel
		/// <summary>
		/// 取得或設定相機型號。
		/// </summary>
		public override string CameraModel
		{
			get
			{
				return this.GetValue<string>(this.FindTags(CameraModelTagKeys));
			}
			set
			{
				this.RemoveTags(CameraModelTagKeys);
				if (value != null)
					this.AddTag(null, CameraModelTagKeys[0], value);
			}
		}
		#endregion
		#region Clone
		/// <summary>
		/// 建立可修改的 ExifImageMetadata 複本。
		/// </summary>
		/// <returns>ExifImageMetadata 複本。</returns>
		public override object Clone()
		{
			return new ExifImageMetadata(this, false);
		}
		#endregion
		#region Comment
		/// <summary>
		/// 取得或設定影像的註解文字。
		/// </summary>
		public override string Comment
		{
			get
			{
				//搜尋 Tag
				IList<ExifTag> tags = this.FindTags(CommentTagKeys);

				//轉成字串
				if (tags.Count >= 1 && tags[0].Type == ExifTagType.Byte)
					return this.ConvertToUnicodeString(tags[0].Data);
				return this.GetValue<string>(tags);
			}
			set
			{
				this.RemoveTags(CommentTagKeys);
				if (value != null)
					this.AddTag(null, CommentTagKeys[0], this.ConvertFromUnicodeString(value));
			}
		}
		#endregion
		#region ConvertFromCoordinate
		URational[] ConvertFromCoordinate(double coordinate)
		{
			//取得度
			coordinate = Math.Abs(coordinate);
			URational[] values = new URational[3];
			values[0] = new URational(1, (uint)coordinate);

			//取得分
			coordinate -= (values[0].Numerator);
			values[1] = new URational(1, (uint)(coordinate * 60));

			//取得秒
			coordinate -= (values[1].Numerator / 60.0);
			values[2] = new URational(1, (uint)(coordinate * 3600));

			//轉換完成
			return values;
		}
		#endregion
		#region ConvertFromUnicodeString
		byte[] ConvertFromUnicodeString(string str)
		{
			if (string.IsNullOrEmpty(str))
				return new byte[0];
			byte[] bytes = new byte[(str.Length + 1) * 2];
			Encoding.Unicode.GetBytes(str, 0, str.Length, bytes, 0);
			return bytes;
		}
		#endregion
		#region ConvertToCoordinate
		double ConvertToCoordinate(ExifTag coordinateTag, ExifTag refTag)
		{
			//確認參數
			if (coordinateTag == null || refTag == null)
				return double.NaN;

			//確認內容
			URational[] coordinateValues = (coordinateTag.ExtractValue() as URational[]);
			if (coordinateValues == null)
			{
				Rational[] anotherValues = (coordinateTag.ExtractValue() as Rational[]);
				if (anotherValues != null)
					coordinateValues = anotherValues.CastToArray<URational>();
			}
			if (coordinateValues == null || coordinateValues.Length < 2)
				return double.NaN;

			//取得度
			double coordinate = coordinateValues[0].ToDouble();

			//取得分
			double minutes = coordinateValues[1].ToDouble();

			//取得秒
			if (coordinateValues.Length > 2)
				minutes += (coordinateValues[2].ToDouble() / 60);
			else
				minutes /= 100;

			//計算位置
			coordinate += (minutes / 60);

			//取得方向
			string direction = (refTag.ExtractValue() as string);
			switch (direction)
			{
				case "N":
				case "E":
					break;
				case "S":
				case "W":
					coordinate = -coordinate;
					break;
				default:
					return double.NaN;
			}

			//轉換完成
			return coordinate;
		}
		#endregion
		#region ConvertToUnicodeString
		string ConvertToUnicodeString(byte[] bytes)
		{
			if (bytes == null)
				return null;
			int length = bytes.Length;
			if (length > 2)
			{
				length -= ((length & 1) == 0 ? 2 : 1);
				return Encoding.Unicode.GetString(bytes, 0, length);
			}
			return "";
		}
		#endregion
		#region CopyFrom
		/// <summary>
		/// 從指定的中繼資料複製屬性。
		/// </summary>
		/// <param name="metadata">指定的中繼資料。</param>
		/// <exception cref="InvalidOperationException">中繼資料為唯讀。</exception>
		public override void CopyFrom(IMetadata metadata)
		{
			//確認狀態
			if (this.IsReadOnly)
				throw new InvalidOperationException();

			//複製
			if (metadata != null && metadata != this)
			{
				//複製 EXIF Tag
				if (metadata is IExifMetadata)
				{
					foreach (ExifTag tag in (metadata as IExifMetadata).ExifTags)
						_ExifTagMap[tag.Key] = tag;
				}
				if (_ExifTags == null)
					_ExifTags = new List<ExifTag>();
				
				//複製一般屬性
				this.Authors = metadata.Authors;
				this.Comment = metadata.Comment;
				this.Copyright = metadata.Copyright;
				this.CreationTime = metadata.CreationTime;
				this.Keywords = metadata.Keywords;
				this.LastWriteTime = metadata.LastWriteTime;
				this.Software = metadata.Software;
				this.Subject = metadata.Subject;
				this.Title = metadata.Title;

				//複製影像屬性
				IImageMetadata imageMetadata = (metadata as IImageMetadata);
				if (imageMetadata != null)
				{
					this.Aperture = imageMetadata.Aperture;
					this.CameraManufacturer = imageMetadata.CameraManufacturer;
					this.CameraModel = imageMetadata.CameraModel;
					this.ExposureCompensation = imageMetadata.ExposureCompensation;
					this.ExposureProgram = imageMetadata.ExposureProgram;
					this.FlashState = imageMetadata.FlashState;
					this.FocalLength = imageMetadata.FocalLength;
					this.FocalLengthIn35mmFilm = imageMetadata.FocalLengthIn35mmFilm;
					this.GeoCoordinate = imageMetadata.GeoCoordinate;
					this.ISO = imageMetadata.ISO;
					this.Orientation = imageMetadata.Orientation;
					this.ShutterSpeed = imageMetadata.ShutterSpeed;
				}
			}
		}
		#endregion
		#region Copyright
		/// <summary>
		/// 取得或設定影像的版權資訊。
		/// </summary>
		public override string Copyright
		{
			get
			{
				return this.GetValue<string>(this.FindTags(CopyrightTagKeys));
			}
			set
			{
				this.RemoveTags(CopyrightTagKeys);
				if (value != null)
					this.AddTag(null, CopyrightTagKeys[0], value);
			}
		}
		#endregion
		#region CreationTime
		/// <summary>
		/// 取得或設定影像建立時間。
		/// </summary>
		public override DateTime? CreationTime
		{
			get
			{
				return this.GetValue<DateTime?>(this.FindTags(CreationTimeTagKeys));
			}
			set
			{
				this.RemoveTags(CreationTimeTagKeys);
				if (value != null)
					this.AddTag(null, CreationTimeTagKeys[0], value.Value);
			}
		}
		#endregion
		#region ExifTags
		/// <summary>
		/// 取得 EXIF Tag 集合。
		/// </summary>
		public IList<ExifTag> ExifTags
		{
			get
			{
				return _ExifTags;
			}
		}
		#endregion
		#region ExposureCompensation
		/// <summary>
		/// 取得或設定曝光補償值。
		/// </summary>
		public override double? ExposureCompensation
		{
			get
			{
				Rational? apexValue = this.GetValue<Rational?>(this.FindTags(ExposureCompensationTagKeys));
				if (apexValue != null)
					return apexValue.Value.ToDouble();
				return null;
			}
			set
			{
				this.RemoveTags(ExposureCompensationTagKeys);
				if (value != null)
					this.AddTag(null, ExposureCompensationTagKeys[0], Rational.FromDouble(value.Value));
			}
		}
		#endregion
		#region ExposureProgram
		/// <summary>
		/// 取得或設定曝光程式。
		/// </summary>
		public override ExposureProgram? ExposureProgram
		{
			get
			{
				ushort? value = this.GetValue<ushort?>(this.FindTags(ExposureProgramTagKeys));
				if (value != null)
					return (ExposureProgram)value.Value;
				return null;
			}
			set
			{
				this.RemoveTags(ExposureProgramTagKeys);
				if (value != null)
					this.AddTag(null, ExposureProgramTagKeys[0], (ushort)value.Value);
			}
		}
		#endregion
		#region FindTag
		/// <summary>
		/// 搜尋第一個符合條件的 EXIF Tag。
		/// </summary>
		/// <param name="key">EXIF Tag 的識別碼。</param>
		/// <returns>第一個符合條件的 EXIF Tag。</returns>
		public ExifTag FindTag(ExifTagKey key)
		{
			ExifTag tag;
			_ExifTagMap.TryGetValue(key, out tag);
			return tag;
		}
		/// <summary>
		/// 搜尋第一個符合條件的 EXIF Tag。
		/// </summary>
		/// <param name="ifd">EXIF Tag 所屬的 IFD。</param>
		/// <param name="id">EXIF Tag ID。</param>
		/// <returns>第一個符合條件的 EXIF Tag。</returns>
		public ExifTag FindTag(Ifd ifd, ushort id)
		{
			return this.FindTag(new ExifTagKey(ifd, id));
		}
		#endregion
		#region FindTags
		/// <summary>
		/// 搜尋所有符合條件的 EXIF Tag，並將結果依條件順序排序。
		/// </summary>
		/// <param name="keys">EXIF Tag 的識別碼。</param>
		/// <returns>符合條件的 EXIF Tag 清單。</returns>
		/// <exception cref="ArgumentNullException">keys 為 Null (Visual Basic 為 Nothing)。</exception>
		public IList<ExifTag> FindTags(IList<ExifTagKey> keys)
		{
			//確認參數
			if (keys == null)
				throw new ArgumentNullException("keys");

			//搜尋 EXIF Tag
			if (keys.Count == 1)
			{
				ExifTag tag;
				if (_ExifTagMap.TryGetValue(keys[0], out tag))
					return new ExifTag[] { tag };
				return new ExifTag[0];
			}
			else
			{
				List<ExifTag> tags = new List<ExifTag>();
				for (int i = 0, keyCount = keys.Count; i < keyCount; ++i)
				{
					ExifTag tag;
					if (_ExifTagMap.TryGetValue(keys[i], out tag))
						tags.Add(tag);
				}
				return tags;
			}
		}
		#endregion
		#region FlashState
		/// <summary>
		/// 取得或設定閃光燈狀態。
		/// </summary>
		public override FlashState? FlashState
		{
			get
			{
				short? value = this.GetValue<short?>(this.FindTags(FlashStateTagKeys));
				if (value != null)
					return Imaging.FlashState.FromExifFlashState(value.Value);
				return null;
			}
			set
			{
				this.RemoveTags(FlashStateTagKeys);
				if (value != null)
					this.AddTag(null, FlashStateTagKeys[0], (short)value.Value.ToExifFlashState());
			}
		}
		#endregion
		#region FocalLength
		/// <summary>
		/// 取得或設定實際焦距，單位為毫米 (mm)。
		/// </summary>
		public override double? FocalLength
		{
			get
			{
				return this.GetValue<double?>(this.FindTags(FocalLengthTagKeys));
			}
			set
			{
				this.RemoveTags(FocalLengthTagKeys);
				if (value != null)
					this.AddTag(null, FocalLengthTagKeys[0], Rational.FromDouble(value.Value));
			}
		}
		#endregion
		#region FocalLengthIn35mmFilm
		/// <summary>
		/// 取得或設定等效焦距，單位為毫米 (mm)。
		/// </summary>
		public override double? FocalLengthIn35mmFilm
		{
			get
			{
				return this.GetValue<double?>(this.FindTags(FocalLengthIn35mmFilmTagKeys));
			}
			set
			{
				this.RemoveTags(FocalLengthIn35mmFilmTagKeys);
				if (value != null)
					this.AddTag(null, FocalLengthIn35mmFilmTagKeys[0], Rational.FromDouble(value.Value));
			}
		}
		#endregion
		#region GeoCoordinate
		/// <summary>
		/// 取得或設定影像的地理座標。
		/// </summary>
		public override GeoCoordinate GeoCoordinate
		{
			get
			{
				//搜尋 EXIF Tag
				ExifTag[] tags = new ExifTag[GpsCoordinateTagKeys.Length];
				for (int i = GpsCoordinateTagKeys.Length - 1; i >= 0; --i)
					_ExifTagMap.TryGetValue(GpsCoordinateTagKeys[i], out tags[i]);
				if (tags[2] == null || tags[3] == null || tags[4] == null || tags[5] == null)
					return null;

				//取得平面座標
				GeoCoordinate coordinate = new GeoCoordinate();
				try
				{
					coordinate.Latitude = this.ConvertToCoordinate(tags[2], tags[3]);
					coordinate.Longitude = this.ConvertToCoordinate(tags[4], tags[5]);
				}
				catch
				{ }

				//取得高度
				if (tags[0] != null && tags[1] != null)
				{
					object altValue = tags[0].ExtractValue();
					object altRefValue = tags[1].ExtractValue();
					if ((altValue is URational || altValue is Rational) && altRefValue is byte)
					{
						coordinate.Altitude = altValue.Cast<double>();
						if (((byte)altRefValue) != 0)
							coordinate.Altitude = -coordinate.Altitude;
					}
				}

				//轉換完成
				return coordinate;
			}
			set
			{
				//移除舊 EXIF Tag
				this.RemoveTags(GpsCoordinateTagKeys);

				//更新 EXIF Tag
				if (value != null && !value.IsUnknown)
				{
					//更新緯度
					double lat = value.Latitude;
					this.AddTag(null, GpsCoordinateTagKeys[2], this.ConvertFromCoordinate(lat));
					this.AddTag(null, GpsCoordinateTagKeys[3], (lat >= 0 ? "N" : "S"));

					//更新經度
					double lng = value.Longitude;
					this.AddTag(null, GpsCoordinateTagKeys[4], this.ConvertFromCoordinate(lng));
					this.AddTag(null, GpsCoordinateTagKeys[5], (lng >= 0 ? "E" : "W"));

					//更新高度
					double alt = value.Altitude;
					if (!double.IsNaN(alt))
					{
						this.AddTag(null, GpsCoordinateTagKeys[0], URational.FromDouble(Math.Abs(alt)));
						this.AddTag(null, GpsCoordinateTagKeys[1], (byte)(alt >= 0 ? 0 : 1));
					}
				}
			}
		}
		#endregion
		#region GetAsReadOnly
		/// <summary>
		/// 取得唯讀的 ExifImageMetadata。
		/// </summary>
		/// <returns>唯讀的 ExifImageMetadata。</returns>
		public override IMetadata GetAsReadOnly()
		{
			if (this.IsReadOnly)
				return this;
			return new ExifImageMetadata(this, true);
		}
		#endregion
		#region GetLensModelFromMakerNote
		string GetLensModelFromMakerNote()
		{
			Ifd makerNoteType = this.MakerNoteType;
			switch (makerNoteType)
			{
				case Ifd.NikonMakerNoteType1:
				case Ifd.NikonMakerNoteType2:
				case Ifd.NikonMakerNoteType3:
					return this.GetLensModelFromNikonMakerNote(makerNoteType);
				default:
					return null;
			}
		}
		#endregion
		#region GetLensModelFromNikonMakerNote
		string GetLensModelFromNikonMakerNote(Ifd makerNoteType)
		{
			//確認格式
			if (makerNoteType != Ifd.NikonMakerNoteType3)
				return null;

			//取得焦距資訊
			string lensString = null;
			ExifTag tag = this.FindTag(NikonLensTagKey);
			if (tag != null && tag.Type == ExifTagType.URational)
			{
				byte[] rawData = tag.Data;
				if (rawData != null && rawData.Length >= 32)
				{
					BitConverter converter = tag.BitConverter;
					URational minFocalLength = new URational(converter.ToUInt32(rawData, 4), converter.ToUInt32(rawData, 0));
					URational maxFocalLength = new URational(converter.ToUInt32(rawData, 12), converter.ToUInt32(rawData, 8));
					URational minFStop = new URational(converter.ToUInt32(rawData, 20), converter.ToUInt32(rawData, 16));
					URational maxFStop = new URational(converter.ToUInt32(rawData, 28), converter.ToUInt32(rawData, 24));
					if (minFocalLength == maxFocalLength)
						lensString = string.Format("{0}mm f/{1:0.0}", (int)minFocalLength.ToDouble(), minFStop.ToDouble());
					else if (minFStop == maxFStop)
						lensString = string.Format("{0}-{1}mm f/{2:0.0}", (int)minFocalLength.ToDouble(), (int)maxFocalLength.ToDouble(), minFStop.ToDouble());
					else
						lensString = string.Format("{0}-{1}mm f/{2:0.0}-{3:0.0}", (int)minFocalLength.ToDouble(), (int)maxFocalLength.ToDouble(), minFStop.ToDouble(), maxFStop.ToDouble());
				}
			}
			if (lensString == null)
				return null;

			//取得其他資訊
			bool? hasAF = null;
			bool? isAFS = null;
			bool? hasVR = null;
			bool? isGLens = null;
			tag = this.FindTag(NikonLensTypeTagKey);
			if (tag != null && tag.Type == ExifTagType.Byte)
			{
				byte[] rawData = tag.Data;
				if (rawData != null && rawData.Length >= 1)
				{
					int flags = rawData[0];
					hasAF = ((flags & 0x1) == 0);
					isAFS = ((flags & 0x2) != 0);
					isGLens = ((flags & 0x4) != 0);
					hasVR = ((flags & 0x8) != 0);
				}
			}

			//組合鏡頭資訊
			if (hasAF != null)
			{
				StringBuilder strBuffer = new StringBuilder();
				if (hasAF == true)
					strBuffer.Append(isAFS == true ? "AF-S" : "AF");
				strBuffer.Append(" Nikkor ");
				strBuffer.Append(lensString);
				if (isGLens == true)
					strBuffer.Append("G");
				if (hasVR == true)
					strBuffer.Append(" VR");
				lensString = strBuffer.ToString();
			}

			//完成
			return lensString;
		}
		#endregion
		#region GetStringArray
		/// <summary>
		/// 從指定的 EXIF Tag 列表中，取出第一個可以轉換成字串陣列的值。
		/// </summary>
		/// <param name="tags">EXIF Tag 列表。</param>
		/// <returns>第一個可以轉換成字串陣列的值，若沒有符合的 EXIF Tag 將傳回 Null (Visual Basic 為 Nothing)。</returns>
		/// <exception cref="ArgumentNullException">tags 為 Null (Visual Basic 為 Nothing)。</exception>
		protected string[] GetStringArray(IList<ExifTag> tags)
		{
			//確認參數
			if (tags == null)
				throw new ArgumentNullException("tags");

			//搜尋內容
			for (int i = 0, count = tags.Count; i < count; ++i)
			{
				string value;
				ExifTag tag = tags[i];
				if (tag != null && tag.ExtractValue().TryCast<string>(out value))
					return value.Split(';');
			}
			return null;
		}
		#endregion
		#region GetValue
		/// <summary>
		/// 從指定的 EXIF Tag 列表中，取出第一個可以轉換成指定型別的值。
		/// </summary>
		/// <typeparam name="T">指定的值型別。</typeparam>
		/// <param name="tags">EXIF Tag 列表。</param>
		/// <returns>第一個可以轉換成指定型別的值，若沒有符合的 EXIF Tag 將傳回型別預設值。</returns>
		/// <exception cref="ArgumentNullException">tags 為 Null (Visual Basic 為 Nothing)。</exception>
		protected T GetValue<T>(IList<ExifTag> tags)
		{
			//確認參數
			if (tags == null)
				throw new ArgumentNullException("tags");

			//搜尋內容
			bool isDateTime = (typeof(T) == typeof(DateTime) || typeof(T) == typeof(DateTime?));
			for (int i = 0, count = tags.Count; i < count; ++i)
			{

				ExifTag tag = tags[i];
				if (tag != null)
				{
					object originalValue = tag.ExtractValue();
					if (!isDateTime)
					{
						T value;
						if (originalValue.TryCast<T>(out value))
							return value;
					}
					else
					{
						string dateTimeString;
						if (originalValue.TryCast<string>(out dateTimeString) && dateTimeString.Length >= 19)
						{
							StringBuilder buffer = new StringBuilder(dateTimeString);
							buffer[4] = '/';
							buffer[7] = '/';
							DateTime dateTime;
							if (DateTime.TryParse(buffer.ToString(), out dateTime))
								return (T)(object)dateTime;
						}
					}
				}
			}
			return default(T);
		}
		#endregion
		#region ISO
		/// <summary>
		/// 取得或設定 ISO 值。
		/// </summary>
		public override int? ISO
		{
			get
			{
				return this.GetValue<int?>(this.FindTags(ISOTagKeys));
			}
			set
			{
				this.RemoveTags(ISOTagKeys);
				if (value != null)
					this.AddTag(null, ISOTagKeys[0], (short)value.Value);
			}
		}
		#endregion
		#region Keywords
		/// <summary>
		/// 取得或設定影像的關鍵字。
		/// </summary>
		public override string[] Keywords
		{
			get
			{
				//搜尋 Tag
				IList<ExifTag> tags = this.FindTags(KeywordsTagKeys);

				//轉成字串
				if (tags.Count >= 1 && tags[0].Type == ExifTagType.Byte)
					return this.ConvertToUnicodeString(tags[0].Data).Split(';');
				return this.GetStringArray(tags);
			}
			set
			{
				this.RemoveTags(KeywordsTagKeys);
				if (value != null)
					this.AddTag(null, KeywordsTagKeys[0], value);
			}
		}
		#endregion
		#region LastWriteTime
		/// <summary>
		/// 取得或設定影像最近更新時間。
		/// </summary>
		public override DateTime? LastWriteTime
		{
			get
			{
				return this.GetValue<DateTime?>(this.FindTags(LastWriteTimeTagKeys));
			}
			set
			{
				this.RemoveTags(LastWriteTimeTagKeys);
				if (value != null)
					this.AddTag(null, LastWriteTimeTagKeys[0], value.Value);
			}
		}
		#endregion
		#region LensManufacturer
		/// <summary>
		/// 取得或設定鏡頭製造商。
		/// </summary>
		public override string LensManufacturer
		{
			get
			{
				return this.GetValue<string>(this.FindTags(LensManufacturerTagKeys));
			}
			set
			{
				this.RemoveTags(LensManufacturerTagKeys);
				if (value != null)
					this.AddTag(null, LensManufacturerTagKeys[0], value);
			}
		}
		#endregion
		#region LensModel
		/// <summary>
		/// 取得或設定鏡頭型號。
		/// </summary>
		public override string LensModel
		{
			get
			{
				string lensModel = this.GetValue<string>(this.FindTags(LensModelTagKeys));
				if (lensModel != null)
					return lensModel;
				return this.GetLensModelFromMakerNote();
			}
			set
			{
				this.RemoveTags(LensModelTagKeys);
				if (value != null)
					this.AddTag(null, LensModelTagKeys[0], value);
			}
		}
		#endregion
		#region MakerNoteType
		Ifd MakerNoteType
		{
			get
			{
				if (_MakerNoteType == null)
				{
					_MakerNoteType = Exif.Ifd.Ifd0;
					for (int i = _ExifTags.Count - 1; i >= 0; --i)
					{
						Ifd ifd = _ExifTags[i].Ifd;
						switch (ifd)
						{
							case Ifd.NikonMakerNoteType1:
							case Ifd.NikonMakerNoteType2:
							case Ifd.NikonMakerNoteType3:
								_MakerNoteType = ifd;
								return ifd;
						}
					}
				}
				return _MakerNoteType.Value;
			}
		}
		#endregion
		#region OnExifTagListChanged
		void OnExifTagListChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			switch (e.Action)
			{
				case NotifyCollectionChangedAction.Add:
					{
						ExifTag oldTag;
						ExifTag newTag = (ExifTag)e.NewItems[0];
						ExifTagKey key = newTag.Key;
						if (_ExifTagMap.TryGetValue(key, out oldTag))
							_ExifTags.Remove(oldTag);
						_ExifTagMap[key] = newTag;
					}
					break;
				case NotifyCollectionChangedAction.Remove:
					_ExifTagMap.Remove(((ExifTag)e.OldItems[0]).Key);
					break;
				case NotifyCollectionChangedAction.Reset:
					_ExifTagMap.Clear();
					foreach (ExifTag tag in _ExifTags)
						_ExifTagMap[tag.Key] = tag;
					break;
			}
			_MakerNoteType = null;
		}
		#endregion
		#region Orientation
		/// <summary>
		/// 取得或設定影像方向。
		/// </summary>
		public override ImageOrientation? Orientation
		{
			get
			{
				short? value = this.GetValue<short?>(this.FindTags(OrientationTagKeys));
				if (value != null)
					return (ImageOrientation)value.Value;
				return null;
			}
			set
			{
				this.RemoveTags(OrientationTagKeys);
				if (value != null)
					this.AddTag(null, OrientationTagKeys[0], (short)value.Value);
			}
		}
		#endregion
		#region RemoveTags
		/// <summary>
		/// 移除所有符合條件的 EXIF Tag。
		/// </summary>
		/// <param name="keys">EXIF Tag 的識別碼。</param>
		/// <returns>移除的 EXIF Tag 數量。</returns>
		/// <exception cref="ArgumentNullException">keys 為 Null (Visual Basic 為 Nothing)。</exception>
		/// <exception cref="InvalidOperationException">中繼資料為唯讀。</exception>
		protected int RemoveTags(IEnumerable<ExifTagKey> keys)
		{
			//確認參數
			if (keys == null)
				throw new ArgumentNullException("keys");
			if (this.IsReadOnly)
				throw new InvalidOperationException();

			//移除 EXIF Tag
			int result = 0;
			if (_ExifTags != null && keys != null)
			{
				foreach (ExifTagKey key in keys)
				{
					ExifTag tag;
					if (_ExifTagMap.TryGetValue(key, out tag))
					{
						_ExifTags.Remove(tag);
						++result;
					}
				}
			}
			return result;
		}
		#endregion
		#region ShutterSpeed
		/// <summary>
		/// 取得或設定快門速度。
		/// </summary>
		public override URational? ShutterSpeed
		{
			get
			{
				Rational? value = this.GetValue<Rational?>(this.FindTags(ShutterSpeedTagKeys));
				if (value != null)
				{
					try
					{
						//約分
						URational shutterSpeed = (URational)value.Value;
						if (shutterSpeed.Numerator <= shutterSpeed.Denominator && shutterSpeed.Numerator != 1 && (shutterSpeed.Denominator % shutterSpeed.Numerator) == 0)
						{
							shutterSpeed.Denominator /= shutterSpeed.Numerator;
							shutterSpeed.Numerator = 1;
						}

						//完成
						return shutterSpeed;
					}
					catch
					{ }
				}
				return null;
			}
			set
			{
				this.RemoveTags(ShutterSpeedTagKeys);
				if (value != null)
					this.AddTag(null, ShutterSpeedTagKeys[0], (Rational)value.Value);
			}
		}
		#endregion
		#region Software
		/// <summary>
		/// 取得或設定製作影像的軟體名稱。
		/// </summary>
		public override string Software
		{
			get
			{
				return this.GetValue<string>(this.FindTags(SoftwareTagKeys));
			}
			set
			{
				this.RemoveTags(SoftwareTagKeys);
				if (value != null)
					this.AddTag(null, SoftwareTagKeys[0], value);
			}
		}
		#endregion
		#region Subject
		/// <summary>
		/// 取得或設定影像的主旨。
		/// </summary>
		public override string Subject
		{
			get
			{
				//搜尋 Tag
				IList<ExifTag> tags = this.FindTags(SubjectTagKeys);

				//轉成字串
				if (tags.Count >= 1 && tags[0].Type == ExifTagType.Byte)
					return this.ConvertToUnicodeString(tags[0].Data);
				return this.GetValue<string>(tags);
			}
			set
			{
				this.RemoveTags(SubjectTagKeys);
				if (value != null)
					this.AddTag(null, SubjectTagKeys[0], this.ConvertFromUnicodeString(value));
			}
		}
		#endregion
		#region Title
		/// <summary>
		/// 取得或設定影像的標題。
		/// </summary>
		public override string Title
		{
			get
			{
				//搜尋 Tag
				IList<ExifTag> tags = this.FindTags(TitleTagKeys);

				//轉成字串
				if (tags.Count >= 1 && tags[0].Type == ExifTagType.Byte)
					return this.ConvertToUnicodeString(tags[0].Data);
				return this.GetValue<string>(tags);
			}
			set
			{
				this.RemoveTags(TitleTagKeys);
				if (value != null)
					this.AddTag(null, TitleTagKeys[0], this.ConvertFromUnicodeString(value));
			}
		}
		#endregion
	}
}
